package com.tianji.learning.utils;
import cn.hutool.core.util.ObjectUtil;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.JsonUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.learning.domain.dto.LearningRecordFormDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import io.swagger.models.auth.In;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * @Author: 19311
 * @CreateTime: 2025-03-25
 * @Description: 延迟任务执行器
 * @Version: 1.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LearningRecordDelayTaskHandler {

    // value完全为字符串
    private final StringRedisTemplate redisTemplate;
    private final String KEY_TEMPLATE = "learning:record:{}";

    // 学习课程服务
    private final ILearningLessonService lessonService;
    // 学习记录服务
    private final LearningRecordMapper learningRecordMapper;

    /**
     * 延迟队列<延迟任务>
     */
    private DelayQueue<DelayTask<DelayLearningRecordDTO>> delayQueue = new DelayQueue<>();

    /**
     * 创建一个延迟检测任务队列
     */
    @PostConstruct
    public void init() throws InterruptedException {
        // 成功地阻塞了主进程
        CompletableFuture.runAsync(new MyTask());
    }

    class MyTask implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    DelayTask<DelayLearningRecordDTO> take = delayQueue.take();
                    DelayLearningRecordDTO data = take.getData();
                    if (ObjectUtils.isEmpty(data)) {
                        continue;
                    }
                    Integer oldMoment = data.moment;
                    // 判断数据前后是否一致，如果moment不一致，说明还在学习，什么也不要做
                    LearningRecord learningRecord = readLearningRecordFromCache(data.getLessonId(), data.getSectionId());
                    // 判断数据是否不一致
                    if (ObjectUtils.isEmpty(learningRecord) || !oldMoment.equals(learningRecord.getMoment())) {
                        continue;
                    }
                    // 一致，说明用户停止了观看, 需要更新学习记录表和我的课表
                    // 为null，代表不会更新这个字段，因为mybatisPlus的默认更新策略为非空才更新
                    learningRecord.setFinished(null);
                    updateLatest(learningRecord);
                } catch (InterruptedException e) {
                    log.error("执行某个延时队列任务失败，原因是", e.getMessage());
                }
            }
        }
    }

    /**
     * 更新最近播放记录进度
     * @param learningRecord
     */
    private void updateLatest(LearningRecord learningRecord) {
        // 更新学习记录表播放进度
        this.learningRecordMapper.updateById(learningRecord);
        // 更新课表最近学习时间和最近学习小节
        this.lessonService.lambdaUpdate()
                .eq(LearningLesson::getId, learningRecord.getLessonId())
                .set(LearningLesson::getLatestLearnTime, LocalDateTime.now().minusSeconds(20))
                .set(LearningLesson::getLatestSectionId, learningRecord.getSectionId())
                .update();
    }
    /**
     * 1、从缓存中读取学习记录
     * @param lessonId 课表ID
     * @return
     */
    public LearningRecord readLearningRecordFromCache(Long lessonId, Long sectionId) {
        // 1、组装大Key
        String key = StringUtils.format(KEY_TEMPLATE, lessonId);

        // 2、组装hashKey
        String hashKey = lessonId + ":" + sectionId;
        String res = (String)redisTemplate.opsForHash().get(key, hashKey);

        if (ObjectUtils.isEmpty(res)) {
            return null;
        }

        // 3、发起查询，得到hashvalue
        LearningRecord learningRecord = JsonUtils.toBean(res, LearningRecord.class);
        return learningRecord;
    }

    /**
     * 清除学习记录缓存
     */
    public void deleteCache(Long lessonId, Long sectionId) {
        // 1、组装大Key
        String key = String.format(KEY_TEMPLATE, lessonId);

        // 2、组装hashKey
        String hashKey = lessonId + ":" + sectionId;

        // 3、发起查询，得到hashvalue
        redisTemplate.opsForHash().delete(key, hashKey);
    }

    /**
     * 添加学习记录延时检测任务
     * 1)、暂存播放记录至redis
     * 2)、提交延时检测任务
     */
    public void writeToCacheAndSubmitTask(LearningRecord learningRecord) {
        // 暂存播放进度到redis
        writeCache(learningRecord);
        DelayLearningRecordDTO delayLearningRecordDTO = new DelayLearningRecordDTO(learningRecord);
        // 添加延迟任务
        DelayTask<DelayLearningRecordDTO> delayTask = new DelayTask<>(Duration.of(20, ChronoUnit.SECONDS), delayLearningRecordDTO);
        delayQueue.add(delayTask);
    }

    public void writeCache(LearningRecord learningRecord) {
        // 1、组装大Key
        String key = StringUtils.format(KEY_TEMPLATE, learningRecord.getLessonId());

        // 2、组装hashKey
        String hashKey = learningRecord.getLessonId() + ":" + learningRecord.getSectionId();

        // 3、放进学习记录
        redisTemplate.opsForHash().put(key, hashKey, JsonUtils.toJsonStr(learningRecord));

        // 3.1 每提交一次学习记录的进度应该有一个针对当前用户当前小节的redis的hashkey来计数提交数量，防止暴力拉动获取积分
        // 这个key应该是用户id加上小节id，值为心跳数和阈值的json字符串。当心跳数超过一定值时，发送到redis的积分排行榜上里完成积分增加。并可以永久删除这个key

        // 4、设置过期时间为1分钟
        redisTemplate.expire(hashKey, Duration.ofMinutes(1));
    }

    @Data
    class DelayLearningRecordDTO {
        private Long lessonId;
        private Long sectionId;
        private Integer moment;

        public DelayLearningRecordDTO(LearningRecord learningRecord) {
            BeanUtils.copyProperties(learningRecord, this);
        }
    }

    /**
     * 第一次自己写没用上，这样子可以节省redis的内存空间
     */
    @Data
    class RedisRecordData {
        private Long id;
        private Long moment;
        private Boolean finished;
    }


}
